﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Threading;
using JetBrains.Annotations;
using MugenMvvmToolkit.Annotations;
using MugenMvvmToolkit.Infrastructure;
using MugenMvvmToolkit.Interfaces;
using MugenMvvmToolkit.Interfaces.ViewModels;
using MugenMvvmToolkit.Models;
using MugenMvvmToolkit.Utils;
#if TASKSUPPORT
using System.Threading.Tasks;
#endif

// ReSharper disable CheckNamespace
namespace MugenMvvmToolkit.ViewModels
// ReSharper restore CheckNamespace
{
    /// <summary>
    ///     Represents the extensions method for mvvm application.
    /// </summary>
    public static class MvvmExtensionsAdvanced
    {
        #region Models extensions

        /// <summary>
        /// Checks whether the properties are equal.
        /// </summary>
        /// <param name="propertyChangedEvent">The specified property changed event.</param>
        /// <param name="getProperty">The expression to get property.</param>
        /// <returns>If true property equals, otherwise false.</returns>
        [Pure]
        public static bool PropertyNameEqual<T>([NotNull]this AsyncValidationMessage propertyChangedEvent, [NotNull] Expression<Func<T, object>> getProperty)
        {
            Should.NotBeNull(propertyChangedEvent, "propertyChangedEvent");
            return MvvmUtilsInternal.ParsePropertyExpression(getProperty) == propertyChangedEvent.PropertyName;
        }

        /// <summary>
        /// Checks whether the properties are equal.
        /// </summary>
        /// <param name="propertyChangedEvent">The specified property changed event.</param>
        /// <param name="getProperty">The expression to get property.</param>
        /// <returns>If true property equals, otherwise false.</returns>
        [Pure]
        public static bool PropertyNameEqual<T>([NotNull]this DataErrorsChangedMessage propertyChangedEvent, [NotNull] Expression<Func<T, object>> getProperty)
        {
            Should.NotBeNull(propertyChangedEvent, "propertyChangedEvent");
            return MvvmUtilsInternal.ParsePropertyExpression(getProperty) == propertyChangedEvent.PropertyName;
        }

#if TASKSUPPORT
        /// <summary>
        ///     Updates information about errors in the specified property.
        /// </summary>
        [SuppressTaskBusyHandler]
        public static Task Validate<T>([NotNull]this T validatableViewModel, [NotNull] Expression<Func<T, object>> getProperty)
            where T : IValidatableViewModel
        {
            Should.NotBeNull(validatableViewModel, "validatableViewModel");
            return validatableViewModel.Validate(MvvmExtensions.GetPropertyName(getProperty));
        }
#else
        /// <summary>
        ///     Updates information about errors in the specified property.
        /// </summary>
        public static IOperationResult Validate<T>([NotNull]this T validatableViewModel, [NotNull] Expression<Func<T, object>> getProperty)
             where T : IValidatableViewModel
        {
            Should.NotBeNull(validatableViewModel, "validatableViewModel");
            return validatableViewModel.Validate(MvvmExtensions.GetPropertyName(getProperty));
        }
#endif


        #endregion

        #region Collections extensions

        /// <summary>
        ///     Sets the specified state for all items in the collection.
        /// </summary>
        /// <param name="collection">The specified collection.</param>
        /// <param name="state">The state value.</param>
        /// <param name="validateState">The flag indicating that state will be validated before assigned.</param>
        public static void SetStateForAll([NotNull]this ITrackingCollection collection, EntityState state, bool validateState = false)
        {
            Should.NotBeNull(collection, "collection");
            foreach (var item in collection.OfType<object>().ToArray())
                collection.UpdateState(item, state, validateState);
        }

        /// <summary>
        ///     Updates states of entities.
        /// </summary>
        /// <param name="collection">The specified collection.</param>
        /// <param name="predicate">The specified condition.</param>
        /// <param name="state">The specified state.</param>
        /// <param name="validateState">The flag indicating that state will be validated before assigned.</param>
        public static void SetStateForAllWhere([NotNull]this ITrackingCollection collection, [NotNull]Func<object, bool> predicate, EntityState state, bool validateState = false)
        {
            Should.NotBeNull(collection, "collection");
            Should.NotBeNull(predicate, "predicate");
            foreach (var item in collection.OfType<object>().Where(predicate).ToArray())
                collection.UpdateState(item, state, validateState);
        }

        /// <summary>
        ///     Updates states of entities.
        /// </summary>
        /// <param name="collection">The specified collection.</param>
        /// <param name="predicate">The specified condition.</param>
        /// <param name="state">The specified state.</param>
        /// <param name="validateState">The flag indicating that state will be validated before assigned.</param>
        public static void SetStateForAllWhere<T>([NotNull]this ITrackingCollection<T> collection, [NotNull] Func<T, bool> predicate, EntityState state, bool validateState = false) where T : class
        {
            Should.NotBeNull(collection, "collection");
            Should.NotBeNull(predicate, "predicate");
            foreach (var item in collection.Where(predicate).ToArray())
                collection.UpdateState(item, state, validateState);
        }

        /// <summary>
        ///     Updates state of entity.
        /// </summary>
        /// <param name="collection">The specified collection.</param>
        /// <param name="item">The item to add.</param>
        /// <param name="validateState">The flag indicating that state will be validated before assigned.</param>
        /// <param name="newState">The state if item is new.</param>
        /// <param name="updateState">The state if item exist in collection.</param>
        public static bool UpdateState([NotNull]this ITrackingCollection collection, [NotNull]object item, EntityState newState, EntityState updateState, bool validateState = false)
        {
            Should.NotBeNull(collection, "collection");
            Should.NotBeNull(item, "item");
            if (collection.Contains(item))
                return collection.UpdateState(item, updateState, validateState);
            return collection.UpdateState(item, newState, validateState);
        }

        /// <summary>
        ///     Updates state of entity.
        /// </summary>
        /// <param name="collection">The specified collection.</param>
        /// <param name="item">The item to add.</param>
        /// <param name="validateState">The flag indicating that state will be validated before assigned.</param>
        public static bool UpdateState([NotNull]this ITrackingCollection collection, [NotNull] IEntityStateEntry item, bool validateState = false)
        {
            Should.NotBeNull(collection, "collection");
            Should.NotBeNull(item, "item");
            return collection.UpdateState(item.Entity, item.State, validateState);
        }

        /// <summary>
        ///     Updates state of entity.
        /// </summary>
        /// <typeparam name="T">Type of collection</typeparam>
        /// <param name="collection">The specified collection.</param>
        /// <param name="item">The item to add.</param>
        /// <param name="validateState">The flag indicating that state will be validated before assigned.</param>
        public static bool UpdateState<T>([NotNull]this ITrackingCollection<T> collection, [NotNull] IEntityStateEntry<T> item, bool validateState = false) where T : class
        {
            Should.NotBeNull(collection, "collection");
            Should.NotBeNull(item, "item");
            return collection.UpdateState(item.Entity, item.State, validateState);
        }

        /// <summary>
        ///     Updates states of entities.
        /// </summary>
        /// <param name="collection">The specified collection.</param>
        /// <param name="items">The range of values.</param>
        /// <param name="state">The specified state.</param>
        /// <param name="validateState">The flag indicating that state will be validated before assigned.</param>
        public static void UpdateStates([NotNull]this ITrackingCollection collection, [NotNull] IEnumerable items, EntityState state, bool validateState = false)
        {
            Should.NotBeNull(items, "items");
            foreach (var value in items)
                collection.UpdateState(value, state, validateState);
        }

        /// <summary>
        ///     Updates states of entities.
        /// </summary>
        /// <param name="collection">The specified collection.</param>
        /// <param name="items">Items to add</param>
        /// <param name="validateState">The flag indicating that state will be validated before assigned.</param>
        public static void UpdateStates([NotNull]this ITrackingCollection collection, [NotNull]IEnumerable<IEntityStateEntry> items, bool validateState = false)
        {
            Should.NotBeNull(collection, "collection");
            Should.NotBeNull(items, "items");
            foreach (var entityStateEntry in items)
                collection.UpdateState(entityStateEntry, validateState);
        }

        /// <summary>
        ///     Updates states of entities.
        /// </summary>
        /// <typeparam name="T">Type of collection</typeparam>
        /// <param name="collection">The specified collection.</param>
        /// <param name="items">Items to add</param>
        /// <param name="validateState">The flag indicating that state will be validated before assigned.</param>
        public static void UpdateStates<T>([NotNull]this ITrackingCollection<T> collection, [NotNull]IEnumerable<IEntityStateEntry<T>> items, bool validateState = false) where T : class
        {
            Should.NotBeNull(collection, "collection");
            Should.NotBeNull(items, "items");
            foreach (var entityStateEntry in items)
                collection.UpdateState(entityStateEntry, validateState);
        }

        /// <summary>
        /// Removes the object from the tracking collection.
        /// </summary>
        /// <param name="collection">The specified collection</param>
        /// <param name="entity">Object to be detached.</param>
        public static bool Detach([NotNull]this ITrackingCollection collection, [NotNull]object entity)
        {
            Should.NotBeNull(collection, "collection");
            Should.NotBeNull(entity, "entity");
            return collection.UpdateState(entity, EntityState.Detached, false);
        }

        /// <summary>
        ///     Saves the state of entities.
        /// </summary>
        /// <param name="stateProvider">The specified <see cref="IEntityStateProvider"/>.</param>
        /// <param name="entities">The specified collection of entities.</param>
        public static IList<object> SaveRange([NotNull]this IEntityStateProvider stateProvider, [NotNull] IEnumerable entities)
        {
            Should.NotBeNull(stateProvider, "stateProvider");
            Should.NotBeNull(entities, "entities");
            var collection = entities.ToNonGenericList();
            var result = new object[collection.Count];
            for (int i = 0; i < collection.Count; i++)
            {
                result[i] = stateProvider.Save(collection[i]);
            }
            return result;
        }

        /// <summary>
        ///     Applies the state of entities.
        /// </summary>
        /// <param name="stateProvider">The specified <see cref="IEntityStateProvider"/>.</param>
        /// <param name="entities">The specified collection of entities.</param>
        public static IList<object> ApplyRange([NotNull]this IEntityStateProvider stateProvider, [NotNull]IEnumerable entities)
        {
            Should.NotBeNull(stateProvider, "stateProvider");
            Should.NotBeNullOrEmpty(entities, "entities");
            var collection = entities.ToNonGenericList();
            var result = new object[collection.Count];
            for (int i = 0; i < collection.Count; i++)
            {
                result[i] = stateProvider.Apply(collection[i]);
            }
            return result;
        }

        /// <summary>
        ///     Cancels the state of entities.
        /// </summary>
        /// <param name="stateProvider">The specified <see cref="IEntityStateProvider"/>.</param>
        /// <param name="entities">The specified collection of entities.</param>
        public static IList<object> CancelRange([NotNull]this IEntityStateProvider stateProvider, [NotNull] IEnumerable entities)
        {
            Should.NotBeNull(stateProvider, "stateProvider");
            Should.NotBeNullOrEmpty(entities, "entities");
            var collection = entities.ToNonGenericList();
            var result = new object[collection.Count];
            for (int i = 0; i < collection.Count; i++)
            {
                result[i] = stateProvider.Cancel(collection[i]);
            }
            return result;
        }

        /// <summary>
        /// Creates a <see cref="FilterableNotifiableCollection{T}"/> from a notifiable collection.
        /// </summary>
        /// <typeparam name="T">The type of collection.</typeparam>
        /// <param name="collection">The specified collection.</param>
        /// <param name="threadManager">The specified <see cref="IThreadManager"/>.</param>
        /// <returns>An instance of <see cref="FilterableNotifiableCollection{T}"/>.</returns>
        public static FilterableNotifiableCollection<T> ToFilterableCollection<T>([NotNull]this IEnumerable<T> collection, IThreadManager threadManager = null)
        {
            Should.NotBeNull(collection, "collection");
            return collection as FilterableNotifiableCollection<T> ??
                   new FilterableNotifiableCollection<T>(collection, threadManager);
        }

        /// <summary>
        /// Creates a <see cref="FilterableNotifiableCollection{T}"/> from a notifiable collection.
        /// </summary>
        /// <typeparam name="T">The type of collection.</typeparam>
        /// <param name="list">The specified collection.</param>
        /// <param name="threadManager">The specified <see cref="IThreadManager"/>.</param>
        /// <returns>An instance of <see cref="FilterableNotifiableCollection{T}"/>.</returns>
        public static FilterableNotifiableCollection<T> ToFilterableCollection<T>([NotNull]this IList<T> list, IThreadManager threadManager = null)
        {
            Should.NotBeNull(list, "list");
            return list as FilterableNotifiableCollection<T> ??
                   new FilterableNotifiableCollection<T>(list, threadManager);
        }

        /// <summary>
        /// Creates a <see cref="TrackingCollection{T}"/> from a notifiable collection.
        /// </summary>
        /// <typeparam name="T">The type of collection.</typeparam>
        /// <param name="collection">The specified collection.</param>
        /// <param name="entityState">The specified entity state.</param>
        /// <param name="transitionProvider">The specified <see cref="IStateTransitionProvider"/>.</param>
        /// <returns>An instance of <see cref="TrackingCollection{T}"/>.</returns>
        public static ITrackingCollection<T> ToTrackingCollection<T>([NotNull]this IEnumerable<T> collection, EntityState entityState = EntityState.Unchanged, IStateTransitionProvider transitionProvider = null) where T : class
        {
            Should.NotBeNull(collection, "collection");
            return collection as ITrackingCollection<T> ??
                         new TrackingCollection<T>(collection, entityState, transitionProvider);
        }

        /// <summary>
        /// Changes type in the specified <see cref="IEntityStateEntry"/>.
        /// </summary>
        public static IEntityStateEntry<T> ChangeType<T>([NotNull]this IEntityStateEntry entityStateEntry) where T : class
        {
            Should.NotBeNull(entityStateEntry, "entityStateEntry");
            var item = entityStateEntry as IEntityStateEntry<T>;
            if (item != null)
                return item;
            return EntityStateEntry.Create(entityStateEntry.State, entityStateEntry.Entity as T);
        }

        /// <summary>
        /// Whether this Entity is unchanged.
        /// </summary>
        [Pure]
        public static bool IsUnchanged(this EntityState es)
        {
            return (es & EntityState.Unchanged) == EntityState.Unchanged;
        }

        /// <summary>
        /// Whether this Entity has been added.
        /// </summary>
        [Pure]
        public static bool IsAdded(this EntityState es)
        {
            return (es & EntityState.Added) == EntityState.Added;
        }

        /// <summary>
        /// Whether this Entity has been modified.
        /// </summary>
        [Pure]
        public static bool IsModified(this EntityState es)
        {
            return (es & EntityState.Modified) == EntityState.Modified;
        }

        /// <summary>
        /// Whether this Entity has been detached (either not yet attached or removed via RemoveFromManager).
        /// </summary>
        [Pure]
        public static bool IsDetached(this EntityState es)
        {
            return (es & EntityState.Detached) == EntityState.Detached;
        }

        /// <summary>
        /// Whether this Entity has been deleted (but the change has not yet been persisted to the data source).
        /// </summary>
        [Pure]
        public static bool IsDeleted(this EntityState es)
        {
            return (es & EntityState.Deleted) == EntityState.Deleted;
        }

        /// <summary>
        /// Whether this Entity has been either added or modified.
        /// </summary>
        [Pure]
        public static bool IsAddedOrModified(this EntityState es)
        {
            return es.IsAdded() || es.IsModified();
        }

        /// <summary>
        /// Whether this Entity has been either added or modified.
        /// </summary>
        [Pure]
        public static bool IsAddedOrModifiedOrUnchanged(this EntityState es)
        {
            return es.IsAddedOrModified() || es.IsUnchanged();
        }

        /// <summary>
        /// Whether this Entity has been added, modified or deleted.
        /// </summary>
        [Pure]
        public static bool IsAddedOrModifiedOrDeleted(this EntityState es)
        {
            return es.IsAddedOrModified() || es.IsDeleted();
        }

        /// <summary>
        /// Whether this Entity has been either deleted or detached.
        /// </summary>
        [Pure]
        public static bool IsDeletedOrDetached(this EntityState es)
        {
            return es.IsDeleted() || es.IsDetached();
        }

        /// <summary>
        /// Whether this Entity has been either deleted or modified
        /// </summary>
        [Pure]
        public static bool IsDeletedOrModified(this EntityState es)
        {
            return es.IsDeleted() || es.IsModified();
        }

        internal static IDictionary<string, IList<object>> CloneDictionary(this IDictionary<string, IEnumerable> dictionary)
        {
            return dictionary
                    .ToDictionary(pair => pair.Key, pair => (IList<object>)pair.Value.OfType<object>().ToArray());
        }

        internal static IDictionary<string, IList<object>> MergeDictionaries(params IDictionary<string, IEnumerable>[] dictionaries)
        {
            if (dictionaries.Length == 0)
                return new Dictionary<string, IList<object>>();
            IEnumerable<string> allKeys = dictionaries[0].Keys;
            for (int i = 1; i < dictionaries.Length; i++)
            {
                allKeys = dictionaries[i].Keys.Concat(allKeys);
            }
            var result = new Dictionary<string, IList<object>>();
            foreach (var key in allKeys)
            {
                var list = new List<object>();
                foreach (var dictionary in dictionaries)
                {
                    IEnumerable values;
                    if (dictionary.TryGetValue(key, out values))
                        list.AddRange(values.OfType<object>());
                }
                result[key] = list;
            }
            return result;
        }

        internal static IDictionary<string, IList<object>> MergeDictionaries(params IDictionary<string, IList<object>>[] dictionaries)
        {
            if (dictionaries.Length == 0)
                return new Dictionary<string, IList<object>>();
            IEnumerable<string> allKeys = dictionaries[0].Keys;
            for (int i = 1; i < dictionaries.Length; i++)
            {
                allKeys = dictionaries[i].Keys.Concat(allKeys);
            }
            var result = new Dictionary<string, IList<object>>();
            foreach (var key in allKeys)
            {
                var list = new List<object>();
                foreach (var dictionary in dictionaries)
                {
                    IList<object> values;
                    if (dictionary.TryGetValue(key, out values))
                        list.AddRange(values);
                }
                result[key] = list;
            }
            return result;
        }

        private static IList ToNonGenericList(this IEnumerable enumerable)
        {
            var list = enumerable as IList;
            return list ?? enumerable.OfType<object>().ToList();
        }

        #endregion

        #region Task extensions

#if TASKSUPPORT
        internal static Task WhenAll(Task[] tasks)
        {
            if (tasks == null)
                return MvvmUtils.TrueTaskResult;
            if (tasks.Length == 0)
                return MvvmUtils.TrueTaskResult;
            if (tasks.Length == 1)
                return tasks[0];
            return Task.Factory.ContinueWhenAll(tasks, tasks1 =>
                                                       {
                                                           foreach (var task in tasks1)
                                                           {
                                                               task.Wait();
                                                           }
                                                       }, TaskContinuationOptions.ExecuteSynchronously);
        }
#else
        internal static IOperationResult WhenAll(IList<IOperationResult> operationResults)
        {
            if (operationResults == null || operationResults.Count == 0)
                return MvvmUtils.TrueOperationResult;

            if (operationResults.Count == 1)
                return operationResults[0];

            var result = new ManualOperationResult<object>();
            bool hasError = false;
            int validatorCount = operationResults.Count;
            int validatedCount = 0;
            foreach (var action in operationResults)
            {
                action.BeginWait(operationResult =>
                {
                    if (hasError) return;
                    if (operationResult.IsFaulted)
                    {
                        hasError = true;
                        result.TrySetException(operationResult.Exception);
                        return;
                    }
                    Interlocked.Increment(ref validatedCount);
                    if (validatorCount == validatedCount)
                        result.SetResult(null);
                });
            }
            return result;
        }
#endif
        #endregion
    }
}